package SubjectBacktracking.Four;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class SolveSudoku {

/**
 * 难度：困难
 * 
 * 37. 解数独
 * 	编写一个程序，通过填充空格来解决数独问题。
 * 	
 * 一个数独的解法需遵循如下规则：
 * 	1.数字 1-9 在每一行只能出现一次。
 * 	2.数字 1-9 在每一列只能出现一次。
 * 	3.数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
 * 	空白格用 '.' 表示。
 * 
 * 提示：
 * 	给定的数独序列只包含数字 1-9 和字符 '.' 。
 * 	你可以假设给定的数独只有唯一解。
 * 	给定数独永远是 9x9 形式的。
 *
 * */
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SolveSudoku ss = new SolveSudoku();
		char[][] board = new char[][] {{'5','3','.','.','7','.','.','.','.'}
									  ,{'6','.','.','1','9','5','.','.','.'}
									  ,{'.','9','8','.','.','.','.','6','.'}
									  ,{'8','.','.','.','6','.','.','.','3'}
									  ,{'4','.','.','8','.','3','.','.','1'}
									  ,{'7','.','.','.','2','.','.','.','6'}
									  ,{'.','6','.','.','.','.','2','8','.'}
									  ,{'.','.','.','4','1','9','.','.','5'}
									  ,{'.','.','.','.','8','.','.','7','9'}};
		ss.solveSudoku(board);
		for(int i=0;i<board.length;i++) {
			System.out.println(new String(board[i]));
		}
	}

	public void solveSudoku(char[][] board) {
		backtracking(board);
    }
	public boolean backtracking(char[][] board) {
		for (int i = 0; i < board.length; i++) {		// 遍历行
			for (int j = 0; j < board[0].length; j++) {	// 遍历列
				if (board[i][j] != '.') continue;
				for (char k = '1'; k <= '9'; k++) {		// (i, j) 这个位置放k是否合适
					if (isValid(i, j, k, board)) { 
						board[i][j] = k;				// 放置k
						if (backtracking(board)) return true;	// 如果找到合适一组立刻返回
						board[i][j] = '.';				// 回溯，撤销k
					}
				}
				return false; // 9个数都试完了，都不行，那么就返回false
			}
		}
		return true; // 遍历完没有返回false，说明找到了合适棋盘位置了
	}
	/*
	 * 判断棋盘是否合法有如下三个维度：
	 * 1.同行是否重复
	 * 2.同列是否重复
	 * 3.9宫格里是否重复
	 * */
	public boolean isValid(int row, int col, char val, char[][] board) {
	    for (int i = 0; i < 9; i++) {	// 判断行里是否重复
	        if (board[row][i] == val) {
	            return false;
	        }
	    }
	    for (int j = 0; j < 9; j++) {	// 判断列里是否重复
	        if (board[j][col] == val) {
	            return false;
	        }
	    }
	    //定位到9方格的左上角
	    int startRow = (row / 3) * 3;
	    int startCol = (col / 3) * 3;
	    for (int i = startRow; i < startRow + 3; i++) { // 判断9方格里是否重复
	        for (int j = startCol; j < startCol + 3; j++) {
	            if (board[i][j] == val ) {
	                return false;
	            }
	        }
	    }
	    return true;
	}
	//方法一：递归
    private boolean[][] line1 = new boolean[9][9];
    private boolean[][] column1 = new boolean[9][9];
    private boolean[][][] block1 = new boolean[3][3][9];
    private boolean valid1 = false;
    private List<int[]> spaces1 = new ArrayList<int[]>();

    public void solveSudoku1(char[][] board) {
        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] == '.') {
                    spaces1.add(new int[]{i, j});
                } else {
                    int digit = board[i][j] - '0' - 1;
                    line1[i][digit] = column1[j][digit] = block1[i / 3][j / 3][digit] = true;
                }
            }
        }

        dfs1(board, 0);
    }
    public void dfs1(char[][] board, int pos) {
        if (pos == spaces1.size()) {
            valid1 = true;
            return;
        }

        int[] space = spaces1.get(pos);
        int i = space[0], j = space[1];
        for (int digit = 0; digit < 9 && !valid1; ++digit) {
            if (!line1[i][digit] && !column1[j][digit] && !block1[i / 3][j / 3][digit]) {
                line1[i][digit] = column1[j][digit] = block1[i / 3][j / 3][digit] = true;
                board[i][j] = (char) (digit + '0' + 1);
                dfs1(board, pos + 1);
                line1[i][digit] = column1[j][digit] = block1[i / 3][j / 3][digit] = false;
            }
        }
    }
	//方法二：位运算优化
    private int[] line2 = new int[9];
    private int[] column2 = new int[9];
    private int[][] block2 = new int[3][3];
    private boolean valid2 = false;
    private List<int[]> spaces2 = new ArrayList<int[]>();

    public void solveSudoku2(char[][] board) {
        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] == '.') {
                    spaces2.add(new int[]{i, j});
                } else {
                    int digit = board[i][j] - '0' - 1;
                    flip(i, j, digit);
                }
            }
        }

        dfs2(board, 0);
    }

    public void dfs2(char[][] board, int pos) {
        if (pos == spaces2.size()) {
            valid2 = true;
            return;
        }

        int[] space = spaces2.get(pos);
        int i = space[0], j = space[1];
        int mask = ~(line2[i] | column2[j] | block2[i / 3][j / 3]) & 0x1ff;
        for (; mask != 0 && !valid2; mask &= (mask - 1)) {
            int digitMask = mask & (-mask);
            int digit = Integer.bitCount(digitMask - 1);
            flip(i, j, digit);
            board[i][j] = (char) (digit + '0' + 1);
            dfs2(board, pos + 1);
            flip(i, j, digit);
        }
    }

    public void flip(int i, int j, int digit) {
        line2[i] ^= (1 << digit);
        column2[j] ^= (1 << digit);
        block2[i / 3][j / 3] ^= (1 << digit);
    }
    //方法三：枚举优化
    private int[] line3 = new int[9];
    private int[] column3 = new int[9];
    private int[][] block3 = new int[3][3];
    private boolean valid3 = false;
    private List<int[]> spaces3 = new ArrayList<int[]>();

    public void solveSudoku3(char[][] board) {
        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] != '.') {
                    int digit = board[i][j] - '0' - 1;
                    flip(i, j, digit);
                }
            }
        }

        while (true) {
            boolean modified = false;
            for (int i = 0; i < 9; ++i) {
                for (int j = 0; j < 9; ++j) {
                    if (board[i][j] == '.') {
                        int mask = ~(line3[i] | column3[j] | block3[i / 3][j / 3]) & 0x1ff;
                        if ((mask & (mask - 1)) == 0) {
                            int digit = Integer.bitCount(mask - 1);
                            flip(i, j, digit);
                            board[i][j] = (char) (digit + '0' + 1);
                            modified = true;
                        }
                    }
                }
            }
            if (!modified) {
                break;
            }
        }

        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] == '.') {
                    spaces3.add(new int[]{i, j});
                }
            }
        }

        dfs3(board, 0);
    }

    public void dfs3(char[][] board, int pos) {
        if (pos == spaces3.size()) {
            valid3 = true;
            return;
        }

        int[] space = spaces3.get(pos);
        int i = space[0], j = space[1];
        int mask = ~(line3[i] | column3[j] | block3[i / 3][j / 3]) & 0x1ff;
        for (; mask != 0 && !valid3; mask &= (mask - 1)) {
            int digitMask = mask & (-mask);
            int digit = Integer.bitCount(digitMask - 1);
            flip(i, j, digit);
            board[i][j] = (char) (digit + '0' + 1);
            dfs3(board, pos + 1);
            flip(i, j, digit);
        }
    }

    public void flip3(int i, int j, int digit) {
        line3[i] ^= (1 << digit);
        column3[j] ^= (1 << digit);
        block3[i / 3][j / 3] ^= (1 << digit);
    }
}
